package com.lhy.mymall.service.impl;

import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.google.zxing.WriterException;
import com.lhy.mymall.common.Constant;
import com.lhy.mymall.exception.ExceptionEnum;
import com.lhy.mymall.exception.MallException;
import com.lhy.mymall.flter.UserFilter;
import com.lhy.mymall.mapper.CartMapper;
import com.lhy.mymall.mapper.OrderItemMapper;
import com.lhy.mymall.mapper.OrderMapper;
import com.lhy.mymall.mapper.ProductMapper;
import com.lhy.mymall.pojo.Order;
import com.lhy.mymall.pojo.OrderItem;
import com.lhy.mymall.pojo.Product;
import com.lhy.mymall.pojo.User;
import com.lhy.mymall.request.CreateOrderReq;
import com.lhy.mymall.service.CartService;
import com.lhy.mymall.service.OrderService;
import com.lhy.mymall.utils.OrderCodeFactory;
import com.lhy.mymall.utils.QRCodeGenerator;
import com.lhy.mymall.vo.CartVO;
import com.lhy.mymall.vo.OrderItemVO;
import com.lhy.mymall.vo.OrderVO;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import static net.sf.jsqlparser.util.validation.metadata.NamedObject.role;

/**
 * @author: 卢辉宇
 * @date: 2022/12/19 16:06
 * @description:
 */
@Service
public class OrderServiceImp implements OrderService {
    @Resource
    CartService cartService;

    @Resource
    ProductMapper productMapper;

    @Resource
    CartMapper cartMapper;

    @Resource
    OrderMapper orderMapper;

    @Resource
    OrderItemMapper orderItemMapper;

    @Value("${qrcode.ip}")
    String ip;

    /**
     * 订单生成方法
     * 启用事务机制，rollbackFor = Exception.class有异常抛出就回滚数据库
     * @param createOrderReq 收件人，收获地址，电话等信息
     * @return 订单号
     * @throws MallException
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public String create(CreateOrderReq createOrderReq) throws MallException {
        /*1.拿到用户id*/
        Integer userId = UserFilter.currentUser.getId();

        /*2.从购物车中查找已勾选的商品*/
        //从购物车中找到全部商品
        List<CartVO> cartVOList = cartService.list(userId);
        //创建一个list用于临时保存已勾选的商品
        List<CartVO> cartVOListTemp = new ArrayList<>();
        //遍历全部商品，选出已经勾选了的商品
        for (int i = 0; i < cartVOList.size(); i++) {
            CartVO cartVO =  cartVOList.get(i);
            if(cartVO.getSelected().equals(Constant.Cart.CHECKED)){
                //商品选中（常量类中有定义CHECKED=1则带表选中）
                //加入已勾选的商品的list
                cartVOListTemp.add(cartVO);
            }
        }
        //得到已勾选的商品
        cartVOList=cartVOListTemp;

        /*3.判断购物车勾选的商品是否为空*/
        if(CollectionUtils.isEmpty(cartVOList)){
            //得到的集合为空，则抛出异常
            throw new MallException(ExceptionEnum.CART_EMPTY);
        }

        /*4.判断商品的存在状态，上下架、库存状态（仿照购物车实现类中验证商品的有效性validProduct方法）*/
        validSaleStatusAndStock(cartVOList);

        /*5.把购物车的对象转化成订单的item对象....*/
        List<OrderItem> orderItemList = cartVOListToOrderItemList(cartVOList);

        /*6.扣库存*/
        //遍历orderItemList,并完成库存的扣除
        for (int i = 0; i < orderItemList.size(); i++) {
            //在数据库中查到此商品
            OrderItem orderItem =  orderItemList.get(i);
            Product product = productMapper.selectById(orderItem.getProductId());
            //算出扣完库存的剩余量
            int stock = product.getStock() - orderItem.getQuantity();
            //有别人抢先扣了库存，库存不足，抛出异常
            if(stock<0){
                throw new MallException(ExceptionEnum.NOT_ENOUGH);
            }
            //扣此商品的库存
            product.setStock(stock);
        }

        /*7.删除购物车已勾选的商品*/
        for (int i = 0; i < cartVOList.size(); i++) {
            CartVO cartVO = cartVOList.get(i);
            cartMapper.deleteById(cartVO.getId());
        }

        /*8生成订单*/
        Order order = new Order();
        //生成主订单号,并赋值给订单对象(需要独立的生成规则。。。)
        String orderCode = OrderCodeFactory.getOrderCode(userId);
        //计算出订单总价格...
        Integer totalPrice = totalPrice(orderItemList);

        //把生成的订单相关字段进行赋值
        order.setOrderNo(orderCode);
        order.setUserId(userId);
        order.setTotalPrice(totalPrice);
        order.setReceiverName(createOrderReq.getReceiverName());
        order.setReceiverAddress(createOrderReq.getReceiverAddress());
        order.setReceiverMobile(createOrderReq.getReceiverMobile());
        //订单状态（在常量类中描述。。。，订单状态: 0用户已取消，10未付款（初始状态），20已付款，30已发货，40交易完成）
        order.setOrderStatus(Constant.OrderStatusEnum.NOT_PAID.getCode());

        //调用mapper层方法插入数据库
        orderMapper.insert(order);

        //循环每个商品保存到order_item表
        for (int i = 0; i < orderItemList.size(); i++) {
            OrderItem orderItem =  orderItemList.get(i);
            //把订单号赋值给单个商品的订单号
            orderItem.setOrderNo(order.getOrderNo());
            //调用orderItemMapper的方法出入数据库
            orderItemMapper.insert(orderItem);
        }

        /*9.返回订单号*/
        return orderCode;
    }

    //计算出订单总价格，单位分
    private Integer totalPrice(List<OrderItem> orderItemList) {
        Integer totalPrice=0;
        for (int i = 0; i < orderItemList.size(); i++) {
            OrderItem orderItem = orderItemList.get(i);
            totalPrice=totalPrice+orderItem.getTotalPrice();
        }
        return totalPrice;
    }

    /**
     * 购物车商品列表转换成订单元素列表方法
     * @param cartVOList 购物车商品列表
     * @return orderItemList 订单元素列表
     */
    private List<OrderItem> cartVOListToOrderItemList(List<CartVO> cartVOList) {
        //新建一个list用于保存OrderItem
        List<OrderItem> orderItemList = new ArrayList<>();

        //遍历每个商品，将每一个CartVO商品转化成OrderItem商品
        for (int i = 0; i < cartVOList.size(); i++) {
            CartVO cartVO =  cartVOList.get(i);
            OrderItem orderItem = new OrderItem();
            //把cartVO的相关值赋值给OrderItem（注：这里不要理所当然的像处理pojo赋值给cartVO那样调用spring的工具复制，因为这里两个表有些字段名字一样，但含义不一样）
            orderItem.setProductId(cartVO.getProductId());
            orderItem.setProductName(cartVO.getProductName());
            orderItem.setProductImg(cartVO.getProductImage());
            orderItem.setUnitPrice(cartVO.getPrice());
            orderItem.setQuantity(cartVO.getQuantity());
            orderItem.setTotalPrice(cartVO.getTotalPrice());
            orderItemList.add(orderItem);
        }
        return orderItemList;
    }

    /**
     * 验证商品的有效性（商品的存在状态，上下架、库存状态）
     * @param cartVOList 购物车商品列表
     * @throws MallException
     */
    private void validSaleStatusAndStock(List<CartVO> cartVOList) throws MallException {
        //遍历判断每一个商品的状态是否满足要求
        for (int i = 0; i < cartVOList.size(); i++) {
            CartVO cartVO =  cartVOList.get(i);
            //在mapper层查询此商品。。。
            Product product = productMapper.selectById(cartVO.getProductId());
            //判断商品是否存在，是否上架（product.getStatus().equals(0)）最好在常量类中定义...
            /*if(product==null||product.getStatus().equals(0)){*/
            if(product==null||product.getStatus().equals(Constant.SaleStatus.NOT_SALE)){
                //不存在或者已下架
                throw new MallException(ExceptionEnum.NOT_SALE);
            }

            //判断商品库存
            if (cartVO.getQuantity()>product.getStock()){
                //库存不足
                throw new MallException(ExceptionEnum.NOT_ENOUGH);
            }
        }

    }

    /**
     * 订单详情业务方法
     * @param orderNo
     * @return 前端展示的封装类
     */
    @Override
    public OrderVO detail(String orderNo) throws MallException {
        //调用mapper层的订单号查找方法，查找到对应的订单。。。
        Order order = orderMapper.selectByOrderNo(orderNo);
        if (order==null){
            throw new MallException(ExceptionEnum.NO_ORDER);
        }
        //订单存在，判断订单是否是本人的
        Integer userId = UserFilter.currentUser.getId();
        if (userId!=order.getUserId()){
            throw new MallException(ExceptionEnum.NOT_YOUR_ORDER);
        }

        //调用一个把order对象转化为orderVO对象的方法...
        OrderVO orderVO=getOrderVO(order);

        return orderVO;
    }

    /**
     * 取消订单，只有未付款状态下才直接可取消
     * @param orderNo
     * @throws MallException
     */
    @Override
    public void cancel(String orderNo) throws MallException {
        //调用mapper层的订单号查找方法，查找到对应的订单
        Order order = orderMapper.selectByOrderNo(orderNo);
        if (order==null){
            throw new MallException(ExceptionEnum.NO_ORDER);
        }
        //订单存在，判断订单是否是本人的
        Integer userId = UserFilter.currentUser.getId();
        if (userId!=order.getUserId()){
            throw new MallException(ExceptionEnum.NOT_YOUR_ORDER);
        }

        //订单未付款时可以直接取消订单
        if(order.getOrderStatus().equals(Constant.OrderStatusEnum.NOT_PAID.getCode())){
            order.setOrderStatus(Constant.OrderStatusEnum.CANCELED.getCode());
            //此订单结束，设置订单结束时间
            order.setEndTime(new Date());
            //调用mapper层方法更新数据库。。。
            orderMapper.update(order);
        }else {
            //非未付款状态
            throw new MallException(ExceptionEnum.CAN_NOT_CANCEL);
        }
    }

    /**
     * 生成支付二维码
     * @param orderNo
     * @return
     */
    @Override
    public String qrcode(String orderNo) throws IOException, WriterException, MallException {
        //通过RequestContextHolder.getRequestAttributes();获取请求相关的信息
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();

        //拿到请求的端口号
        int port = request.getLocalPort();

        //自己在配置中规定的ip...
        //得到”http://ip:port/pay?orderNo=订单号“字符串作为生成二维码的内容
        String payUrl = "http://" + ip + ":" + port + "/pay?orderNo=" + orderNo;

        //创建文件夹,保存的地址在常量类中定义...
        File fileDir = new File(Constant.FILE_QRCODE_DIR);
        //判断是否存在文件夹
        if(!fileDir.exists()){
            //不存在，新建
            if (!fileDir.mkdir()){
                //新建失败，抛出异常
                throw new MallException(ExceptionEnum.MKDIR_FAILED);
            }
        }

        //调用编写的工具类生成二维码,
        QRCodeGenerator.generateQRCodeImage(payUrl,400,400,Constant.FILE_QRCODE_DIR+orderNo+".png");

        String pngAddress="http://"+ip+":"+port+"/qrcode/"+orderNo+".png";
        return pngAddress;
    }

    /**
     * 后台订单列表
     * @param pageNum
     * @param pageSize
     * @return 分好页的全部订单列表
     */
    @Override
    public PageInfo listForAdmin(Integer pageNum, Integer pageSize) throws MallException {
        //开启分页
        PageHelper.startPage(pageNum,pageSize);

        //调用mapper方法，在数据库中找出所有的订单。。。
        List<Order> orderList=orderMapper.selectAllForAdmin();

        //把orderList转化层orderListVO...
        List<OrderVO> orderVOList = orderListToOderListVO(orderList);

        //分页封装
        PageInfo<OrderVO> pageInfo = new PageInfo<>(orderVOList);


        return pageInfo;
    }

    /**
     * 前台用户列表
     * @param pageNum
     * @param pageSize
     * @return
     */
    @Override
    public PageInfo listForCustomer(Integer pageNum, Integer pageSize) throws MallException {
        //从过滤器中获取当前用户的id
        Integer userId = UserFilter.currentUser.getId();

        //开启分页
        PageHelper.startPage(pageNum, pageSize);

        //调用mapper层，查找出此用户的所有订单。。。
        List<Order> orderList = orderMapper.selectByUserId(userId);

        //把orderList转化层orderListVO...
        List<OrderVO> orderVOList = orderListToOderListVO(orderList);
        //封装成分页的
        PageInfo<OrderVO> pageInfo = new PageInfo<>(orderVOList);

        return pageInfo;
    }

    /**
     * 支付接口
     * @param orderNo
     * @throws MallException
     */
    @Override
    public void pay(String orderNo) throws MallException {
        //查出此订单
        Order order = orderMapper.selectByOrderNo(orderNo);

        if (order==null){
            throw new MallException(ExceptionEnum.NO_ORDER);
        }

        //查看订单是否处于未付款状态
        if (order.getOrderStatus()==Constant.OrderStatusEnum.NOT_PAID.getCode()){
            //改变订单状态
            order.setOrderStatus(Constant.OrderStatusEnum.PAID.getCode());
            //设置付款时间
            order.setPayTime(new Date());

            //调用mapper的方法写入数据库
            orderMapper.update(order);
        }else {
            //非未付款状态
            throw new MallException(ExceptionEnum.NOT_PAY_STATUS);
        }
    }

    /**
     * 发货接口
     * @param orderNo
     * @throws MallException
     */
    @Override
    public void delivered(String orderNo) throws MallException {
        //查出此订单
        Order order = orderMapper.selectByOrderNo(orderNo);

        if (order==null){
            throw new MallException(ExceptionEnum.NO_ORDER);
        }

        //查看订单是否处于已付款状态
        if (order.getOrderStatus()==Constant.OrderStatusEnum.PAID.getCode()){
            //改变订单状态
            order.setOrderStatus(Constant.OrderStatusEnum.DELIVERED.getCode());
            //设置付款时间
            order.setDeliveryTime(new Date());

            //调用mapper的方法写入数据库
            orderMapper.update(order);
        }else {
            //非已付款状态
            throw new MallException(ExceptionEnum.NOT_DELIVERED_STATUS);
        }
    }

    /**
     * 订单完结
     * @param orderNo
     * @throws MallException
     */
    @Override
    public void finish(String orderNo) throws MallException {
        //查出此订单
        Order order = orderMapper.selectByOrderNo(orderNo);
        if (order==null){
            //订单不存在
            throw new MallException(ExceptionEnum.NO_ORDER);
        }

        /*判断是用户端完结订单还是管理员端完结订单*/
        //拿到当前登录用户
        User currentUser = UserFilter.currentUser;
        /*1是普通用户，2是管理员*/
        if(currentUser.getRole().equals(1)){
            //是普通用户，继续判断此订单是否是本人的订单
            if(!currentUser.getId().equals(order.getUserId())){
                throw new MallException(ExceptionEnum.NOT_YOUR_ORDER);
            }
        }

        //判断订单是否为发货状态
        if (order.getOrderStatus()==Constant.OrderStatusEnum.DELIVERED.getCode()){
            //更改状态
            order.setOrderStatus(Constant.OrderStatusEnum.FINISHED.getCode());
            //写入完结时间
            order.setEndTime(new Date());

            //修改数据库
            orderMapper.update(order);
        }else {
            throw new MallException(ExceptionEnum.NOT_DELIVERED_STATUS);
        }
    }

    /**
     * 把orderList转化成orderListVO
     * @param orderList
     * @return orderVOList
     * @throws MallException
     */
    private List<OrderVO> orderListToOderListVO(List<Order> orderList) throws MallException {

        List<OrderVO> orderVOList=new ArrayList<>();

        for (int i = 0; i < orderList.size(); i++) {
            Order order =  orderList.get(i);
            OrderVO orderVO = new OrderVO();
            orderVO = getOrderVO(order);
            orderVOList.add(orderVO);
        }
        return orderVOList;
    }

    /**
     * 获取orderVO对象的方法
     * @param order
     * @return
     */
    private OrderVO getOrderVO(Order order) throws MallException {
        //存放的是总的那个
        OrderVO orderVO = new OrderVO();
        //把对应字段复制
        BeanUtils.copyProperties(order,orderVO);

        //把orderItemVOList的部分也赋值过来
        //在orderItemMapper层查找orderItemVOList。。。
        List<OrderItem> orderItemList = orderItemMapper.selectByOrderNo(order.getOrderNo());
        //存放的是里面各个小的订单
        List<OrderItemVO> orderItemVOList = new ArrayList<>();

        //遍历查到的orderItemList
        for (int i = 0; i < orderItemList.size(); i++) {
            OrderItem orderItem =  orderItemList.get(i);
            OrderItemVO orderItemVO = new OrderItemVO();
            //把orderItem复制到orderItemVO对应字段
            BeanUtils.copyProperties(orderItem,orderItemVO);

            //把它加入到orderItemVOList集合中
            orderItemVOList.add(orderItemVO);
        }

        //给orderVO状态的名称信息赋值（根据状态码，通过在常量类中定义的枚举找到状态信息...）
        orderVO.setOrderStatusName(Constant.OrderStatusEnum.codeOf(order.getOrderStatus()).getMsg());

        //把各个小的订单的集合加入到总订单的小订单集合字段里
        orderVO.setOrderItemVOList(orderItemVOList);

        return orderVO;
    }


}
